/*
 * %W% %E%
 * 
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. SUN
 * PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.tools.doclets.internal.toolkit.util;

import com.sun.javadoc.*;
import com.sun.tools.doclets.internal.toolkit.taglets.*;
import java.util.*;

/**
 * Search for the requested documentation. Inherit documentation if necessary.
 * 
 * @author Jamie Ho
 * @since 1.5
 */
public class DocFinder {
	
	/**
	 * The class that encapsulates the input.
	 */
	public static class Input {
		/**
		 * The method to search documentation from.
		 */
		public MethodDoc method = null;
		/**
		 * The taglet to search for documentation on behalf of. Null if we want
		 * to search for overall documentation.
		 */
		public InheritableTaglet taglet = null;
		
		/**
		 * The id of the tag to retrieve documentation for.
		 */
		public String tagId = null;
		
		/**
		 * The tag to retrieve documentation for. This is only used for the
		 * inheritDoc tag.
		 */
		public Tag tag = null;
		
		/**
		 * True if we only want to search for the first sentence.
		 */
		public boolean isFirstSentence = false;
		
		/**
		 * True if we are looking for documentation to replace the
		 * inheritDocTag.
		 */
		public boolean isInheritDocTag = false;
		
		/**
		 * Used to distinguish between type variable param tags and regular
		 * param tags.
		 */
		public boolean isTypeVariableParamTag = false;
		
		public Input() {
		}
		
		public Input(MethodDoc method, InheritableTaglet taglet, Tag tag,
				boolean isFirstSentence, boolean isInheritDocTag) {
			this.method = method;
			this.taglet = taglet;
			this.tag = tag;
			this.isFirstSentence = isFirstSentence;
			this.isInheritDocTag = isInheritDocTag;
		}
		
		public Input(MethodDoc method, InheritableTaglet taglet, String tagId) {
			this.method = method;
			this.taglet = taglet;
			this.tagId = tagId;
		}
		
		public Input(MethodDoc method, InheritableTaglet taglet, String tagId,
				boolean isTypeVariableParamTag) {
			this.method = method;
			this.taglet = taglet;
			this.tagId = tagId;
			this.isTypeVariableParamTag = isTypeVariableParamTag;
		}
		
		public Input(MethodDoc method, InheritableTaglet taglet) {
			this.method = method;
			this.taglet = taglet;
		}
		
		public Input(MethodDoc method) {
			this.method = method;
		}
		
		public Input(MethodDoc method, boolean isFirstSentence) {
			this.method = method;
			this.isFirstSentence = isFirstSentence;
		}
		
		public Input copy() {
			Input clone = new Input();
			clone.method = this.method;
			clone.taglet = this.taglet;
			clone.tagId = this.tagId;
			clone.tag = this.tag;
			clone.isFirstSentence = this.isFirstSentence;
			clone.isInheritDocTag = this.isInheritDocTag;
			clone.isTypeVariableParamTag = this.isTypeVariableParamTag;
			return clone;
			
		}
	}
	
	/**
	 * The class that encapsulates the output.
	 */
	public static class Output {
		/**
		 * The tag that holds the documentation. Null if documentation is not
		 * held by a tag.
		 */
		public Tag holderTag;
		
		/**
		 * The Doc object that holds the documentation.
		 */
		public Doc holder;
		
		/**
		 * The inherited documentation.
		 */
		public Tag[] inlineTags = new Tag[] {};
		
		/**
		 * False if documentation could not be inherited.
		 */
		public boolean isValidInheritDocTag = true;
		
		/**
		 * When automatically inheriting throws tags, you sometime must inherit
		 * more than one tag. For example if the method declares that it throws
		 * IOException and the overidden method has throws tags for IOException
		 * and ZipException, both tags would be inherited because ZipException
		 * is a subclass of IOException. This subclass of DocFinder.Output
		 * allows multiple tag inheritence.
		 */
		public List tagList = new ArrayList();
	}
	
	/**
	 * Search for the requested comments in the given method. If it does not
	 * have comments, return documentation from the overriden method if
	 * possible. If the overriden method does not exist or does not have
	 * documentation to inherit, search for documentation to inherit from
	 * implemented methods.
	 * 
	 * @param input the input object used to perform the search.
	 * 
	 * @return an Output object representing the documentation that was found.
	 */
	public static Output search(Input input) {
		Output output = new Output();
		if (input.isInheritDocTag) {
			// Do nothing because "method" does not have any documentation.
			// All it has it {@inheritDoc}.
		} else if (input.taglet == null) {
			// We want overall documentation.
			output.inlineTags = input.isFirstSentence
					? input.method.firstSentenceTags()
					: input.method.inlineTags();
			output.holder = input.method;
		} else {
			input.taglet.inherit(input, output);
		}
		
		if (output.inlineTags != null && output.inlineTags.length > 0) {
			return output;
		}
		output.isValidInheritDocTag = false;
		Input inheritedSearchInput = input.copy();
		inheritedSearchInput.isInheritDocTag = false;
		if (input.method.overriddenMethod() != null) {
			inheritedSearchInput.method = input.method.overriddenMethod();
			output = search(inheritedSearchInput);
			output.isValidInheritDocTag = true;
			if (output.inlineTags.length > 0) {
				return output;
			}
		}
		// NOTE: When we fix the bug where ClassDoc.interfaceTypes() does
		// not pass all implemented interfaces, we will use the
		// appropriate method here.
		MethodDoc[] implementedMethods = (new ImplementedMethods(input.method,
				null)).build();
		for (int i = 0; i < implementedMethods.length; i++) {
			inheritedSearchInput.method = implementedMethods[i];
			output = search(inheritedSearchInput);
			output.isValidInheritDocTag = true;
			if (output.inlineTags.length > 0) {
				return output;
			}
		}
		return output;
	}
}
